home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 September / PCWorld_2008-09_cd.bin / v cisle / sadanastroju / lightning-0.8-tb-win.xpi / components / calCompositeCalendar.js < prev    next >
Text File  |  2008-02-25  |  21KB  |  631 lines

  1. /* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is Oracle Corporation code.
  16.  *
  17.  * The Initial Developer of the Original Code is
  18.  *  Oracle Corporation
  19.  * Portions created by the Initial Developer are Copyright (C) 2004
  20.  * the Initial Developer. All Rights Reserved.
  21.  *
  22.  * Contributor(s):
  23.  *   Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
  24.  *
  25.  * Alternatively, the contents of this file may be used under the terms of
  26.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  27.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28.  * in which case the provisions of the GPL or the LGPL are applicable instead
  29.  * of those above. If you wish to allow use of your version of this file only
  30.  * under the terms of either the GPL or the LGPL, and not to allow others to
  31.  * use your version of this file under the terms of the MPL, indicate your
  32.  * decision by deleting the provisions above and replace them with the notice
  33.  * and other provisions required by the GPL or the LGPL. If you do not delete
  34.  * the provisions above, a recipient may use your version of this file under
  35.  * the terms of any one of the MPL, the GPL or the LGPL.
  36.  *
  37.  * ***** END LICENSE BLOCK ***** */
  38.  
  39. //
  40. // calCompositeCalendar.js
  41. //
  42.  
  43. const calIOperationListener = Components.interfaces.calIOperationListener;
  44.  
  45. function calCompositeCalendarObserverHelper (compCalendar) {
  46.     this.compCalendar = compCalendar;
  47.     this.pendingLoads = {};
  48. }
  49.  
  50. calCompositeCalendarObserverHelper.prototype = {
  51.     pendingLoads: null,
  52.  
  53.     onStartBatch: function() {
  54.         this.compCalendar.mObservers.notify("onStartBatch");
  55.     },
  56.  
  57.     onEndBatch: function() {
  58.         this.compCalendar.mObservers.notify("onEndBatch");
  59.     },
  60.  
  61.     onLoad: function(calendar) {
  62.         // avoid unnecessary onLoad events:
  63.         if (this.pendingLoads[calendar.id]) {
  64.             // don't forward if caused by composite:
  65.             delete this.pendingLoads[calendar.id];
  66.         } else {
  67.             // any refreshed dependent calendar logically refreshes
  68.             // this composite calendar, thus we send out an onLoad
  69.             // for this composite calendar:
  70.             this.compCalendar.mObservers.notify("onLoad", [this.compCalendar]);
  71.         }
  72.     },
  73.  
  74.     onAddItem: function(aItem) {
  75.         this.compCalendar.mObservers.notify("onAddItem", arguments);
  76.     },
  77.  
  78.     onModifyItem: function(aNewItem, aOldItem) {
  79.         this.compCalendar.mObservers.notify("onModifyItem", arguments);
  80.     },
  81.  
  82.     onDeleteItem: function(aDeletedItem) {
  83.         this.compCalendar.mObservers.notify("onDeleteItem", arguments);
  84.     },
  85.  
  86.     onError: function(aErrNo, aMessage) {
  87.         this.compCalendar.mObservers.notify("onError", arguments);
  88.     },
  89.  
  90.     onPropertyChanged: function(aCalendar, aName, aValue, aOldValue) {
  91.         this.compCalendar.mObservers.notify("onPropertyChanged", arguments);
  92.     },
  93.  
  94.     onPropertyDeleting: function(aCalendar, aName) {
  95.         this.compCalendar.mObservers.notify("onPropertyDeleting", arguments);
  96.     }
  97. };
  98.  
  99. function calCompositeCalendar () {
  100.     this.mObserverHelper = new calCompositeCalendarObserverHelper(this);
  101.     this.wrappedJSObject = this;
  102.  
  103.     this.mCalendars = new Array();
  104.     this.mCompositeObservers = new calListenerBag(Components.interfaces.calICompositeObserver);
  105.     this.mObservers = new calListenerBag(Components.interfaces.calIObserver);
  106.     this.mDefaultCalendar = null;
  107. }
  108.  
  109. calCompositeCalendar.prototype = {
  110.     //
  111.     // private members
  112.     //
  113.     mDefaultCalendar: null,
  114.  
  115.     //
  116.     // nsISupports interface
  117.     //
  118.     QueryInterface: function (aIID) {
  119.         if (!aIID.equals(Components.interfaces.nsISupports) &&
  120.             !aIID.equals(Components.interfaces.calICalendarProvider) &&
  121.             !aIID.equals(Components.interfaces.calICalendar) &&
  122.             !aIID.equals(Components.interfaces.calICompositeCalendar))
  123.         {
  124.             throw Components.results.NS_ERROR_NO_INTERFACE;
  125.         }
  126.  
  127.         return this;
  128.     },
  129.  
  130.     //
  131.     // calICalendarProvider interface
  132.     //
  133.     get prefChromeOverlay() {
  134.         return null;
  135.     },
  136.  
  137.     get displayName() {
  138.         return calGetString("calendar", "compositeName");
  139.     },
  140.  
  141.     createCalendar: function comp_createCal() {
  142.         throw NS_ERROR_NOT_IMPLEMENTED;
  143.     },
  144.  
  145.     deleteCalendar: function comp_deleteCal(cal, listener) {
  146.         // You shouldn't be able to delete from the composite calendar.
  147.         throw NS_ERROR_NOT_IMPLEMENTED;
  148.     },
  149.  
  150.     //
  151.     // calICompositeCalendar interface
  152.     //
  153.  
  154.     mCalendars: null,
  155.     mDefaultCalendar: null,
  156.     mPrefPrefix: null,
  157.     mDefaultPref: null,
  158.     mActivePref: null,
  159.     
  160.     set prefPrefix (aPrefPrefix) {
  161.         if (this.mPrefPrefix) {
  162.             this.mCalendars.forEach(this.removeCalendar, this);
  163.         }
  164.         this.mPrefPrefix = aPrefPrefix;
  165.         this.mActivePref = aPrefPrefix + "-in-composite";
  166.         this.mDefaultPref = aPrefPrefix + "-default";
  167.         var mgr = getCalendarManager();
  168.         var cals = mgr.getCalendars({});
  169.  
  170.         cals.forEach(function (c) {
  171.             if (c.getProperty(this.mActivePref))
  172.                 this.addCalendar(c);
  173.             if (c.getProperty(this.mDefaultPref))
  174.                 this.setDefaultCalendar(c, false);
  175.         }, this);
  176.     },
  177.  
  178.     get prefPrefix () {
  179.         return this.mPrefPrefix;
  180.     },
  181.  
  182.     addCalendar: function (aCalendar) {
  183.         // check if the calendar already exists
  184.         for each (cal in this.mCalendars) {
  185.             if (aCalendar.uri.equals(cal.uri)) {
  186.                 // throw exception if calendar already exists?
  187.                 return;
  188.             }
  189.         }
  190.  
  191.         // add our observer helper
  192.         aCalendar.addObserver(this.mObserverHelper); // XXX Never removed!
  193.  
  194.         this.mCalendars.push(aCalendar);
  195.         if (this.mPrefPrefix) {
  196.             aCalendar.setProperty(this.mActivePref, true);
  197.         }
  198.         this.mCompositeObservers.notify("onCalendarAdded", [aCalendar]);
  199.  
  200.         // if we have no default calendar, we need one here
  201.         if (this.mDefaultCalendar == null)
  202.             this.setDefaultCalendar(aCalendar, false);
  203.     },
  204.  
  205.     removeCalendar: function (aServer) {
  206.         var newCalendars = Array();
  207.         var calToRemove = null;
  208.         for each (cal in this.mCalendars) {
  209.             if (!aServer.equals(cal.uri))
  210.                 newCalendars.push(cal);
  211.             else
  212.                 calToRemove = cal;
  213.         }
  214.  
  215.         if (calToRemove) {
  216.             this.mCalendars = newCalendars;
  217.             if (this.mPrefPrefix) {
  218.                 calToRemove.deleteProperty(this.mActivePref);
  219.                 calToRemove.deleteProperty(this.mDefaultPref);
  220.             }   
  221.             calToRemove.removeObserver(this.mObserverHelper);
  222.             this.mCompositeObservers.notify("onCalendarRemoved", [calToRemove]);
  223.         }
  224.     },
  225.  
  226.     getCalendar: function (aServer) {
  227.         for each (cal in this.mCalendars) {
  228.             if (aServer.equals(cal.uri))
  229.                 return cal;
  230.         }
  231.  
  232.         return null;
  233.     },
  234.  
  235.     get calendars() {
  236.         // return a nsISimpleEnumerator of this array.  This sucks.
  237.         // XXX make this an array, like the calendar manager?
  238.         return null;
  239.     },
  240.  
  241.     get defaultCalendar() { 
  242.         return this.mDefaultCalendar;
  243.     },
  244.  
  245.     setDefaultCalendar: function (cal, usePref) {
  246.         // don't do anything if the passed calendar is the default calendar!
  247.         if (this.mDefaultCalendar && cal && this.mDefaultCalendar.uri.equals(cal.uri))
  248.             return;
  249.         if (usePref && this.mPrefPrefix) {
  250.             if (this.mDefaultCalendar) {
  251.                 this.mDefaultCalendar.deleteProperty(this.mDefaultPref);
  252.             }
  253.             // if not null set the new calendar as default in the preferences
  254.             if (cal)  {
  255.                 cal.setProperty(this.mDefaultPref, true);
  256.             }
  257.         }
  258.         this.mDefaultCalendar = cal;
  259.         this.mCompositeObservers.notify("onDefaultCalendarChanged", [cal]);
  260.     },
  261.  
  262.     set defaultCalendar(v) {
  263.         this.setDefaultCalendar(v, true);
  264.     },
  265.  
  266.     //
  267.     // calICalendar interface
  268.     //
  269.     // Write operations here are forwarded to either the item's
  270.     // parent calendar, or to the default calendar if one is set.
  271.     // Get operations are sent to each calendar.
  272.     //
  273.  
  274.     get id() {
  275.         throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  276.     },
  277.     set id(id) {
  278.         throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  279.     },
  280.  
  281.     get superCalendar() {
  282.         // There shouldn't be a superCalendar for the composite
  283.         return this;
  284.     },
  285.     set superCalendar(val) {
  286.         throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  287.     },
  288.  
  289.     // this could, at some point, return some kind of URI identifying
  290.     // all the child calendars, thus letting us create nifty calendar
  291.     // trees.
  292.     get uri() {
  293.         throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  294.     },
  295.     set uri(v) {
  296.         throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  297.     },
  298.  
  299.     get readOnly() { 
  300.         throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  301.     },
  302.     set readOnly(bool) {
  303.         throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  304.     },
  305.  
  306.     get canRefresh() {
  307.         return true;
  308.     },
  309.  
  310.     get name() {
  311.         throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  312.     },
  313.     set name(v) {
  314.         throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  315.     },
  316.  
  317.     get type() {
  318.         return "composite";
  319.     },
  320.  
  321.     getProperty: function(aName) {
  322.         throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  323.     },
  324.     setProperty: function(aName, aValue) {
  325.         throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  326.     },
  327.     deleteProperty: function(aName) {
  328.         throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  329.     },
  330.  
  331.     // void addObserver( in calIObserver observer );
  332.     mCompositeObservers: null,
  333.     mObservers: null,
  334.     addObserver: function (aObserver) {
  335.         if (aObserver instanceof Components.interfaces.calICompositeObserver) {
  336.             this.mCompositeObservers.add(aObserver);
  337.         }
  338.         this.mObservers.add(aObserver);
  339.     },
  340.  
  341.     // void removeObserver( in calIObserver observer );
  342.     removeObserver: function (aObserver) {
  343.         if (aObserver instanceof Components.interfaces.calICompositeObserver) {
  344.             this.mCompositeObservers.remove(aObserver);
  345.         }
  346.         this.mObservers.remove(aObserver);
  347.     },
  348.  
  349.     refresh: function() {
  350.         for each (cal in this.mCalendars) {
  351.             try {
  352.                 if (cal.canRefresh) {
  353.                     this.mObserverHelper.pendingLoads[cal.id] = true;
  354.                     cal.refresh();
  355.                 }
  356.             } catch (e) {
  357.                 ASSERT(false, e);
  358.                 delete this.mObserverHelper.pendingLoads[cal.id];
  359.             }
  360.         }
  361.         // send out a single onLoad for this composite calendar,
  362.         // although e.g. the ics provider will trigger another
  363.         // onLoad asynchronously; we cannot rely on every calendar
  364.         // sending an onLoad:
  365.         this.mObservers.notify("onLoad", [this]);
  366.     },
  367.  
  368.     // void modifyItem( in calIItemBase aNewItem, in calIItemBase aOldItem, in calIOperationListener aListener );
  369.     modifyItem: function (aNewItem, aOldItem, aListener) {
  370.         if (aNewItem.calendar == null) {
  371.             // XXX Can't modify item with NULL parent
  372.             throw Components.results.NS_ERROR_FAILURE;
  373.         }
  374.  
  375.         return aNewItem.calendar.modifyItem(aNewItem, aOldItem, aListener);
  376.     },
  377.  
  378.     // void deleteItem( in string id, in calIOperationListener aListener );
  379.     deleteItem: function (aItem, aListener) {
  380.         if (aItem.calendar == null) {
  381.             // XXX Can't delete item with NULL parent
  382.             throw Components.results.NS_ERROR_FAILURE;
  383.         }
  384.  
  385.         return aItem.calendar.deleteItem(aItem, aListener);
  386.     },
  387.  
  388.     // void addItem( in calIItemBase aItem, in calIOperationListener aListener );
  389.     addItem: function (aItem, aListener) {
  390.         return this.mDefaultCalendar.addItem(aItem, aListener);
  391.     },
  392.  
  393.     // void getItem( in string aId, in calIOperationListener aListener );
  394.     getItem: function (aId, aListener) {
  395.         var cmpListener = new calCompositeGetListenerHelper(this.mCalendars.length, aListener);
  396.         for each (cal in this.mCalendars) {
  397.             try {
  398.                 cmpListener.opGroup.add(cal.getItem(aId, cmpListener));
  399.             } catch (exc) {
  400.                 ASSERT(false, exc);
  401.             }
  402.         }
  403.         return cmpListener.opGroup;
  404.     },
  405.  
  406.     // void getItems( in unsigned long aItemFilter, in unsigned long aCount, 
  407.     //                in calIDateTime aRangeStart, in calIDateTime aRangeEnd,
  408.     //                in calIOperationListener aListener );
  409.     getItems: function (aItemFilter, aCount, aRangeStart, aRangeEnd, aListener) {
  410.         // If there are no calendars, then we just call onOperationComplete
  411.         if (this.mCalendars.length == 0) {
  412.             aListener.onOperationComplete (this,
  413.                                            Components.results.NS_OK,
  414.                                            calIOperationListener.GET,
  415.                                            null,
  416.                                            null);
  417.             return;
  418.         }
  419.  
  420.         var cmpListener = new calCompositeGetListenerHelper(this.mCalendars.length, aListener, aCount);
  421.         for (cal in this.mCalendars) {
  422.             try {
  423.                 cmpListener.opGroup.add(
  424.                     this.mCalendars[cal].getItems(
  425.                         aItemFilter, aCount, aRangeStart, aRangeEnd, cmpListener));
  426.             } catch (exc) {
  427.                 ASSERT(false, exc);
  428.             }
  429.         }
  430.         return cmpListener.opGroup;
  431.     },
  432.  
  433.     startBatch: function ()
  434.     {
  435.         this.mCompositeObservers.notify("onStartBatch");
  436.     },
  437.     endBatch: function ()
  438.     {
  439.         this.mCompositeObservers.notify("onEndBatch");
  440.     }
  441. };
  442.  
  443. // composite listener helper
  444. function calCompositeGetListenerHelper(aNumQueries, aRealListener, aMaxItems) {
  445.     this.wrappedJSObject = this;
  446.     this.mNumQueries = aNumQueries;
  447.     this.mRealListener = aRealListener;
  448.     this.mMaxItems = aMaxItems;
  449. }
  450.  
  451. calCompositeGetListenerHelper.prototype = {
  452.     mNumQueries: 0,
  453.     mRealListener: null,
  454.     mOpGroup: null,
  455.     mReceivedCompletes: 0,
  456.     mFinished: false,
  457.     mMaxItems: 0,
  458.     mItemsReceived: 0,
  459.  
  460.     get opGroup() {
  461.         if (!this.mOpGroup) {
  462.             var this_ = this;
  463.             function cancelFunc() { // operation group has been cancelled
  464.                 var listener = this_.mRealListener;
  465.                 this_.mRealListener = null;
  466.                 if (listener) {
  467.                     listener.onOperationComplete(
  468.                         this_, Components.interfaces.calIErrors.OPERATION_CANCELLED,
  469.                         calIOperationListener.GET, null, null);
  470.                 }
  471.             }
  472.             this.mOpGroup = new calOperationGroup(cancelFunc);
  473.         }
  474.         return this.mOpGroup;
  475.     },
  476.  
  477.     QueryInterface: function (aIID) {
  478.         if (!aIID.equals(Components.interfaces.nsISupports) &&
  479.             !aIID.equals(Components.interfaces.calIOperationListener))
  480.         {
  481.             throw Components.results.NS_ERROR_NO_INTERFACE;
  482.         }
  483.  
  484.         return this;
  485.     },
  486.  
  487.     onOperationComplete: function (aCalendar, aStatus, aOperationType, aId, aDetail) {
  488.         if (!this.mRealListener) {
  489.             // has been cancelled, ignore any providers firing on this...
  490.             return;
  491.         }
  492.         if (this.mFinished) {
  493.             dump ("+++ calCompositeGetListenerHelper.onOperationComplete: called with mFinished == true!");
  494.             return;
  495.         }
  496.  
  497.         if (!Components.isSuccessCode(aStatus)) {
  498.             // proxy this to a onGetResult
  499.             // XXX - do we want to give the real calendar? or this?
  500.             // XXX - get rid of iid param
  501.             this.mRealListener.onGetResult (aCalendar, aStatus, 
  502.                                             Components.interfaces.nsISupports,
  503.                                             aDetail, 0, []);
  504.         }
  505.  
  506.         this.mReceivedCompletes++;
  507.  
  508.         if (this.mReceivedCompletes == this.mNumQueries) {
  509.             // we're done here.
  510.             this.mFinished = true;
  511.             this.opGroup.notifyCompleted();
  512.             this.mRealListener.onOperationComplete (this,
  513.                                                     aStatus,
  514.                                                     calIOperationListener.GET,
  515.                                                     null,
  516.                                                     null);
  517.         }
  518.     },
  519.  
  520.     onGetResult: function (aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
  521.         if (!this.mRealListener) {
  522.             // has been cancelled, ignore any providers firing on this...
  523.             return;
  524.         }
  525.         if (this.mFinished) {
  526.             dump ("+++ calCompositeGetListenerHelper.onGetResult: called with mFinished == true!");
  527.             return;
  528.         }
  529.  
  530.         // ignore if we have a max and we're past it
  531.         if (this.mMaxItems && this.mItemsReceived >= this.mMaxItems)
  532.             return;
  533.  
  534.         if (Components.isSuccessCode(aStatus) &&
  535.             this.mMaxItems &&
  536.             ((this.mItemsReceived + aCount) > this.mMaxItems))
  537.         {
  538.             // this will blow past the limit
  539.             aCount = this.mMaxItems - this.mItemsReceived;
  540.             aItems = aItems.slice(0, aCount);
  541.         }
  542.  
  543.         // send GetResults to the real listener
  544.         this.mRealListener.onGetResult (aCalendar, aStatus, aItemType, aDetail, aCount, aItems);
  545.         this.mItemsReceived += aCount;
  546.     }
  547.  
  548. };
  549.  
  550. // nsIFactory
  551. const calCompositeCalendarFactory = {
  552.     createInstance: function (outer, iid) {
  553.         if (outer != null)
  554.             throw Components.results.NS_ERROR_NO_AGGREGATION;
  555.         return (new calCompositeCalendar()).QueryInterface(iid);
  556.     }
  557. };
  558.  
  559. /****
  560.  **** module registration
  561.  ****/
  562.  
  563. var calCompositeCalendarModule = {
  564.     mCID: Components.ID("{aeff788d-63b0-4996-91fb-40a7654c6224}"),
  565.     mContractID: "@mozilla.org/calendar/calendar;1?type=composite",
  566.  
  567.     mUtilsLoaded: false,
  568.     loadUtils: function compositeLoadUtils() {
  569.         if (this.mUtilsLoaded)
  570.             return;
  571.  
  572.         const jssslContractID = "@mozilla.org/moz/jssubscript-loader;1";
  573.         const jssslIID = Components.interfaces.mozIJSSubScriptLoader;
  574.  
  575.         const iosvcContractID = "@mozilla.org/network/io-service;1";
  576.         const iosvcIID = Components.interfaces.nsIIOService;
  577.  
  578.         var loader = Components.classes[jssslContractID].getService(jssslIID);
  579.         var iosvc = Components.classes[iosvcContractID].getService(iosvcIID);
  580.  
  581.         // Note that unintuitively, __LOCATION__.parent == .
  582.         // We expect to find utils in ./../js
  583.         var appdir = __LOCATION__.parent.parent;
  584.         appdir.append("js");
  585.         var scriptName = "calUtils.js";
  586.  
  587.         var f = appdir.clone();
  588.         f.append(scriptName);
  589.  
  590.         try {
  591.             var fileurl = iosvc.newFileURI(f);
  592.             loader.loadSubScript(fileurl.spec, this.__parent__.__parent__);
  593.         } catch (e) {
  594.             dump("Error while loading " + fileurl.spec + "\n");
  595.             throw e;
  596.         }
  597.  
  598.         this.mUtilsLoaded = true;
  599.     },
  600.     
  601.     registerSelf: function (compMgr, fileSpec, location, type) {
  602.         compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  603.         compMgr.registerFactoryLocation(this.mCID,
  604.                                         "Calendar composite provider",
  605.                                         this.mContractID,
  606.                                         fileSpec,
  607.                                         location,
  608.                                         type);
  609.     },
  610.  
  611.     getClassObject: function (compMgr, cid, iid) {
  612.         if (!cid.equals(this.mCID))
  613.             throw Components.results.NS_ERROR_NO_INTERFACE;
  614.  
  615.         if (!iid.equals(Components.interfaces.nsIFactory))
  616.             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  617.  
  618.         this.loadUtils();
  619.  
  620.         return calCompositeCalendarFactory;
  621.     },
  622.  
  623.     canUnload: function(compMgr) {
  624.         return true;
  625.     }
  626. };
  627.  
  628. function NSGetModule(compMgr, fileSpec) {
  629.     return calCompositeCalendarModule;
  630. }
  631.